Skip to content

Feature: NumPy-compliant distributed advanced indexing - #938

Open
ClaudiaComito wants to merge 408 commits into
mainfrom
914_adv-indexing-outshape-outsplit
Open

ClaudiaComito wants to merge 408 commits into
mainfrom
914_adv-indexing-outshape-outsplit

Conversation

@ClaudiaComito

@ClaudiaComito ClaudiaComito commented Mar 24, 2022

Copy link
Copy Markdown
Member

Description

TL;DR

This PR replaces Heat's legacy, local-only indexing with 100% NumPy-API-compliant advanced indexing capabilities across distributed nodes.

You can now seamlessly use boolean masks, integer arrays, negative-step slices etc. on distributed arrays without manual data shuffling:

import heat as ht

# Load a massive distributed dataset (e.g., 100 million rows, split across nodes/GPUs)
data = ht.random.randn(100_000_000, 50, split=0) 

#  distributed filtering condition 
outliers_mask = (data > 3.0).any(axis=1)

# extract only the outliers without gathering to a single node
outliers = data[outliers_mask]

This pull request introduces a significant overhaul of distributed indexing within dndarray.py, specifically targeting the __getitem__ and __setitem__ methods.

The logic has been completely refactored to identify zero-communication paths ("early out") for standard slices, while routing heavy, unordered (non-sequential) advanced indexing through highly optimized MPI collective communication.

Also, indexing.nonzero(), the kwarg as_tuple has been introduced (default: True) to comply with the Numpy API while giving users the choice to switch to torch-style output (2-D array). Merged separately in #2332

Main changes (LATEST UPDATE 18.9.2026)

dndarray.py

  • replaced scattered key parsing with a centralized _resolve_indexing_state helper. This function torch-proofs all key inputs, handles broadcasting, aligns array dimension to indexed shape, and determines the indexing operation type for later dispatching. Returns a structured ProcessedKey NamedTuple.
  • refactored the monolithic __getitem__ and __setitem__ functions. They are now wrappers that call the resolution state and dispatch to dedicated methods (e.g., __getitem_scalar, __setitem_mask, __getitem_advanced_local).
  • added full support for non-sequential distributed advanced indexing (using integer arrays or boolean masks). It uses MPI.Alltoallv for cross-rank data fetching and assignment (__getitem_unordered and __setitem_unordered).
  • introduced _resolve_duplicate_indices to guarantee NumPy-compliant "last assignment wins" semantics when using advanced indexing with duplicate indices on GPUs (thanks @Hakdag97 ).
  • implemented support for descending/negative-step slices across distributed memory.
  • added a __broadcast_value helper to automatically broadcast assigned values to match the target slice or boolean-mask shape during __setitem__ operations.
  • updated __torch_proxy__ to explicitly track the split axis natively within the tensor's named dimensions for safer split axis tracking during dimensions-changing operations. discarded as PyTorch named tensors are no longer supported
  • NEW updated __torch_proxy__ to use PyTorch meta tensors, to perform lightweight shape and index validation without allocating tensor memory
  • refactored tests, removed orphaned legacy code and tests.
  • introduced INDEXING.md in doc/source/ and added it to the .rst index this will be addressed in a different PR.

indexing.py

Changes to the indexing module have been merged with #2332.

Summary of distribution semantics (UPDATED 16.9.2026)

Array is distributed Operation Key is distributed Value is distributed Result is distributed Notes
No array[key] No -- No Standard local indexing directly on underlying torch tensor.
No array[key] Yes -- Yes For a 1D distributed key, the output inherits split and balanced status from the key.
Yes array[key] No -- Yes / No Scalar key on split axis collapses that dimension, output is replicated on each process (split=None). For all other key types distribution is maintained.
Yes array[key] Yes -- Yes Local path: Aligned boolean mask flattens locally with 0 communication.
Communication path: Unordered distributed integer indices trigger __getitem_unordered with Alltoallv exchange.
No array[key] = val No No No (In-place) In-place assignment directly on underlying tensor.
Yes array[key] = val No No Yes (In-place) Scalars: Assigned directly with 0 communication (PyTorch broadcasts locally).
Local arrays: Converted to a distributed array matching the target split axis and aligned via redistribute_.
Yes array[key] = val No Yes Yes (In-place) Split axis match required: If value.split != target.split, raises a RuntimeError.
Yes array[key] = val Yes No, scalar Yes (In-place) Python scalars and 0-D tensors assign directly to all local masked/indexed positions.
Yes array[key] = val Yes No, array ERROR / Yes Supported only for boolean mask key, otherwise ValueError is raised.
Yes array[key] = val Yes Yes Yes (In-place) Aligned boolean mask key: Local assignment with 0 communication.
Unordered integer indices: key is redistributed to match value, followed by a dual Alltoallv shuffle (indices and data payload).

Note: Extracting a single element along the split axis will collapse that dimension, resulting in split=None.

Internal getitem/setitem routing logic

UPDATE 16.9.2026

graph TD
    Start((Receive Key)) --> CheckDist{Is array distributed?}
    
    CheckDist -- No --> LocalFastPath[Unwrap key & index underlying tensor directly]
    CheckDist -- Yes --> CheckScalar{Is key a pure scalar<br/>and not boolean?}
    
    CheckScalar -- Yes --> EvalRoot{Compute root rank}
    EvalRoot --> OpScalar[op_type = 'scalar']
    
    CheckScalar -- No --> CheckDistrMaskEarly{Is key a boolean mask<br/>aligned with array?}
    CheckDistrMaskEarly -- Yes --> OpDistrMask1[op_type = 'distr_mask']
    
    CheckDistrMaskEarly -- No --> ResolveKeys[Resolve key & check bounds]
    
    ResolveKeys --> AssessOpType{_assess_op_type}
    
    AssessOpType -->|root is not None| OpScalar[op_type = 'scalar']
    AssessOpType -->|split_key_is_ordered == 0| OpDist[op_type = 'distributed']
    AssessOpType -->|split_key_is_ordered == -1| OpDesc[op_type = 'descending_slice']
    AssessOpType -->|distr_mask_fast_path| OpDistrMask2[op_type = 'distr_mask']
    AssessOpType -->|key_is_mask_like| OpLocalMask[op_type = 'local_mask']
    AssessOpType -->|Default / Ordered / Slices| OpLocal[op_type = 'local']

    %% Map to actual handlers
    subgraph Handlers [Target dispatch methods]
        OpScalar --> H_Scalar[__getitem_scalar<br/>__setitem_scalar]
        OpDist --> H_Dist[__getitem_advanced_distributed<br/>__setitem_advanced_distributed]
        OpDesc --> H_Desc[__getitem_descending_slice_distributed<br/>__setitem_descending_slice_distributed]
        OpDistrMask1 & OpDistrMask2 --> H_DistMask[__getitem_mask<br/>__setitem_mask]
        OpLocalMask & OpLocal --> H_Local[__getitem_local<br/>__setitem_local]
    end
    
    %% Styling
    classDef target fill:#d4edda,stroke:#28a745,stroke-width:2px;
    class H_Scalar,H_Dist,H_Desc,H_DistMask,H_Local target;
Loading

Memory footprint

Scaling behaviour

Issue/s resolved: #703 #914 #918 #1012 #1019 #2135 #1816 #824

Type of change

  • Breaking change

Memory requirements

Performance

Will follow

Due Diligence

  • All split configurations tested
  • Multiple dtypes tested in relevant functions
  • Documentation updated (if needed)
  • Updated changelog.md under the title "Pending Additions"

Does this change modify the behaviour of other functions? If so, which?

yes, everything that relied on the legacy indexing quirks (fixed) and everything that relied on 2D output from nonzero() (also fixed)

AI usage

We used frontier models (GPT, Gemini 3.1Pro - 3.8 extended), to help refactor, test, debug, optimize this PR, and to write the documentation.

@ClaudiaComito
ClaudiaComito changed the base branch from features/914_adv-indexing to main February 10, 2023 17:49
@ClaudiaComito ClaudiaComito changed the title 914 adv indexing outshape outsplit Expand distributed indexing, match numpy indexing scheme Feb 10, 2023
@ghost

ghost commented Jul 26, 2023

Copy link
Copy Markdown
👇 Click on the image for a new way to code review

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map legend

@mrfh92

mrfh92 commented Oct 27, 2023

Copy link
Copy Markdown
Collaborator

just a comment: in the fft-module (if already merged at time merging this PR) some commented-out parts of test_rfftn_irfftn can be added again if this PR is ready

@codecov

codecov Bot commented Nov 27, 2023

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.27%. Comparing base (c941645) to head (0b7299a).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #938      +/-   ##
==========================================
+ Coverage   83.77%   84.27%   +0.50%     
==========================================
  Files         105      105              
  Lines       15849    16488     +639     
==========================================
+ Hits        13277    13895     +618     
- Misses       2572     2593      +21     
Flag Coverage Δ
unit 84.27% <100.00%> (+0.50%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@ClaudiaComito ClaudiaComito modified the milestones: 1.4.0, 1.5.0 Apr 12, 2024
@github-actions

Copy link
Copy Markdown
Contributor

This pull request is stale because it has been open for 60 days with no activity.

@github-actions github-actions Bot added the stale label Jul 29, 2024
@ClaudiaComito ClaudiaComito modified the milestones: 1.5.0, 1.6 Aug 26, 2024
@github-actions github-actions Bot removed the stale label Sep 2, 2024
@github-actions

Copy link
Copy Markdown
Contributor

This pull request is stale because it has been open for 60 days with no activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core enhancement New feature or request indexing linalg testing Implementation of tests, or test-related issues

Projects

Status: In Progress

6 participants